﻿#region Apache 2.0 License header

//   Copyright 2012 Lesley van Zijl
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License. 

#endregion

using System;
using System.Collections.Generic;
using System.Linq;

namespace FavourObjects
{
    /// <summary>
    /// Comparer that is able to compare two types all the way down to the generic types.
    /// </summary>
    public class DeepTypeComparer : IEqualityComparer<Type>
    {
        #region IEqualityComparer<Type> Members

        /// <summary>
        /// Determines whether the specified objects are equal.
        /// </summary>
        /// <param name="x">The first type to compare.</param>
        /// <param name="y">The second type to compare.</param>
        /// <returns>true if the specified objects are equal; otherwise, false.</returns>
        public bool Equals(Type x, Type y)
        {
            return AreEqual(x, y);
        }

        /// <summary>
        /// Returns a hash code for the specified object.
        /// </summary>
        /// <param name="obj">The object for which a hash code is to be returned.</param>
        /// <exception cref="System.ArgumentNullException">The type of <paramref name="obj"/> is a reference type and 
        /// <paramref name="obj"/> is null.</exception>
        /// <returns>A hash code for the specified object.</returns>
        public int GetHashCode(Type obj)
        {
            if (obj == null)
            {
                throw new ArgumentNullException("obj");
            }

            return obj.GetHashCode();
        }

        #endregion

        /// <summary>
        /// The actual comparing.
        /// </summary>
        /// <param name="x">The first type to compare.</param>
        /// <param name="y">The second type to compare.</param>
        /// <returns>true if the specified objects are equal; otherwise, false.</returns>
        private static bool AreEqual(Type x, Type y)
        {
            bool result = false;

            if (x.HasElementType && y.HasElementType)
            {
                x = x.GetElementType();
                y = y.GetElementType();
            }

            if (x.IsGenericType && y.IsGenericType)
            {
                Type[] thisArguments = x.GetGenericArguments();
                Type[] arguments = y.GetGenericArguments();
                if (thisArguments.Length == arguments.Length)
                {
                    result = !thisArguments.Where((t, i) => !AreEqual(t, arguments[i])).Any();
                }
            }
            else if (x == y)
            {
                result = true;
            }
            else if (x.IsGenericParameter && y.IsGenericParameter)
            {
                result = true;
            }

            return result;
        }
    }
}